﻿using System;
using System.Xml;
using System.Xml.Serialization;
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using System.Xml.Schema;
using System.IO;
using System.Text;
using System.Xml.Linq;
using System.Linq;
using System.Configuration;

using XmlVisualizer.Controllers;
using XmlVisualizer.Models;
using XmlVisualizer.Logging;
using GuiLabs.Undo;

namespace XmlVisualizer.Models {

	/// <summary>
	/// Document mode enumeration.
	/// </summary>
	public enum Mode : int {
		Xml,
		BigXml,
		Xsd,
		Dtd
	}

	/// <summary>
	/// Contains all the data essential for the application.
	/// Controls the behaviour of the program.
	/// </summary>
	public class ApplicationModel : IAppModel {
		#region Fields
		private XmlModel xmlModel = null;
		private XsdModel xsdModel = null;
		private BigXmlModel bigXmlModel = null;

		private Mode mode = Mode.Xml;
		public static ActionManager actionManager = new ActionManager();
		private bool eventHandlersAttached = false;
		#endregion

		#region Properties
		/// <summary>
		/// Gets the XmlModel.
		/// </summary>
		public XmlModel XmlModel {
			get { return xmlModel; }
		}

		/// <summary>
		/// Gets the XsdModel.
		/// </summary>
		public XsdModel XsdModel {
			get { return xsdModel; }
		}

		/// <summary>
		/// Gets or sets the Mode.
		/// </summary>
		public Mode Mode {
			get { return mode; }
			set { mode = value; }
		}
		#endregion

		#region Events
		public event DocumentChangedDelegate DocumentChanged;
		public event Action<object, EventArgs> DocumentSaved;
		public event Action<object, EventArgs> DocumentLoaded;
		public event Action<object, EventArgs> DocumentLoading;
		public event Action<object, DisplayXmlEventArgs> DisplayXml;
		public event Action<object, EventArgs> Shutdown;
		public event Action<object, EventArgs> DocumentClosed;
		public event Action<object, WellformednessEventArgs> DocumentWellformednessChanged;
		public event Action<object, NodeChangedEventArgs> NodeChanged;
		public event Action<object, SelectionChangedEventArgs> SelectionChanged;
		public event Action ModeChanged;
		public event Action<object, XmvEventArgs> SaveXmv;
		public event Action<object, XmvEventArgs> OpenXmv;
		public event Action<object, ValidityEventArgs> DocumentValidityChanged;
		#endregion

		#region Event handlers
		void xmlModel_Show(object sender, DisplayXmlEventArgs e) {
			OnDisplayXml(e);
		}

		void xmlModel_DocumentSaved(object sender, EventArgs e) {
			OnDocumentSaved(e);
		}

		void xmlModel_DocumentLoading(object sender, EventArgs e) {
			OnDocumentLoading(e);
		}

		void xmlModel_DocumentLoaded(object sender, EventArgs e) {
			OnDocumentLoaded(e);
		}

		void xmlModel_DocumentChanged(object sender, DocumentChangedEventArgs e) {
			OnDocumentChanged(e);
		}

		void xmlModel_DocumentClosed(object sender, EventArgs e) {
			OnDocumentClosed(e);
		}

		void xmlModel_DocumentWellformednessChanged(object sender, WellformednessEventArgs e) {
			OnWellformednessChanged(e);
		}

		void xmlModel_NodeChanged(object sender, NodeChangedEventArgs e) {
			OnNodeChanged(e);
		}

		void xmlModel_SelectionChanged(object sender, SelectionChangedEventArgs e) {
			OnSelectionChanged(e);
		}

		void xmlModel_SaveXmv(object sender, XmvEventArgs e) {
			OnSaveXmv(e);
		}

		void xmlModel_OpenXmv(object sender, XmvEventArgs e) {
			OnOpenXmv(e);
		}

		void xmlModel_DocumentValidityChanged(object sender, ValidityEventArgs e) {
			OnDocumentValidityChanged(e);
		}

		/// <summary>
		/// Invoked when document changes.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnDocumentChanged(DocumentChangedEventArgs e) {
			if (DocumentChanged != null)
				DocumentChanged(this, e);
		}

		/// <summary>
		/// Invoked when document gets saved.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnDocumentSaved(EventArgs e) {
			if (DocumentSaved != null)
				DocumentSaved(this, e);
		}

		/// <summary>
		/// Invoked when document is loaded.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnDocumentLoaded(EventArgs e) {
			if (DocumentLoaded != null)
				DocumentLoaded(this, e);
		}

		/// <summary>
		/// Invoked when document is loading.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnDocumentLoading(EventArgs e) {
			if (DocumentLoading != null)
				DocumentLoading(this, e);
		}

		/// <summary>
		/// Invoked when is the right time to display the document.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnDisplayXml(DisplayXmlEventArgs e) {
			if (DisplayXml != null)
				DisplayXml(this, e);
		}

		/// <summary>
		/// Invoked when app is shutting down.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnShutdown(EventArgs e) {
			if (Shutdown != null)
				Shutdown(this, e);
		}

		/// <summary>
		/// Invoked when document gets closed.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnDocumentClosed(EventArgs e) {
			if (DocumentClosed != null)
				DocumentClosed(this, e);
		}

		/// <summary>
		/// Invoked when document's well formedness changes.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnWellformednessChanged(WellformednessEventArgs e) {
			if (DocumentWellformednessChanged != null)
				DocumentWellformednessChanged(this, e);
		}

		/// <summary>
		/// Invoked when node's representation is updated.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnNodeChanged(NodeChangedEventArgs e) {
			if (NodeChanged != null)
				NodeChanged(this, e);
		}

		/// <summary>
		/// Invoked when selection changes.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnSelectionChanged(SelectionChangedEventArgs e) {
			if (SelectionChanged != null)
				SelectionChanged(this, e);
		}

		protected virtual void OnModeChanged() {
			if (ModeChanged != null)
				ModeChanged();
		}

		/// <summary>
		/// Invoked when model want view to save xmv file.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnSaveXmv(XmvEventArgs e) {
			if (SaveXmv != null)
				SaveXmv(this, e);
		}

		/// <summary>
		/// Invoked when model wants view to open xmv file.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnOpenXmv(XmvEventArgs e) {
			if (OpenXmv != null)
				OpenXmv(this, e);
		}

		/// <summary>
		/// Invoked when document's validity changes.
		/// </summary>
		/// <param name="e">The event data.</param>
		protected virtual void OnDocumentValidityChanged(ValidityEventArgs e) {
			if (DocumentValidityChanged != null)
				DocumentValidityChanged(this, e);
		}
		#endregion

		#region Constructors
		/// <summary>
		/// Initializes a new instance of the ApplicationModel class.
		/// </summary>
		public ApplicationModel() {
			xmlModel = new XmlModel(actionManager);
			bigXmlModel = new BigXmlModel(xmlModel);

			if (!eventHandlersAttached)
				AttachEventHandlers();
		}
		#endregion

		#region Methods

		private void AttachEventHandlers() {
			xmlModel.DocumentChanged += new DocumentChangedDelegate(xmlModel_DocumentChanged);
			xmlModel.DocumentLoaded += new Action<object, EventArgs>(xmlModel_DocumentLoaded);
			xmlModel.DocumentLoading += new Action<object, EventArgs>(xmlModel_DocumentLoading);
			xmlModel.DocumentSaved += new Action<object, EventArgs>(xmlModel_DocumentSaved);
			xmlModel.DisplayXml += new Action<object, DisplayXmlEventArgs>(xmlModel_Show);
			xmlModel.DocumentClosed += new Action<object, EventArgs>(xmlModel_DocumentClosed);
			xmlModel.DocumentWellformednessChanged += new Action<object, WellformednessEventArgs>(xmlModel_DocumentWellformednessChanged);
			xmlModel.NodeChanged += new Action<object, NodeChangedEventArgs>(xmlModel_NodeChanged);
			xmlModel.SelectionChanged += new Action<object, SelectionChangedEventArgs>(xmlModel_SelectionChanged);
			xmlModel.SaveXmv += new Action<object, XmvEventArgs>(xmlModel_SaveXmv);
			xmlModel.OpenXmv += new Action<object, XmvEventArgs>(xmlModel_OpenXmv);
			xmlModel.DocumentValidityChanged += new Action<object, ValidityEventArgs>(xmlModel_DocumentValidityChanged);
			eventHandlersAttached = true;
		}
	
		private void DetachEventHandlers() {
			xmlModel.DocumentChanged -= new DocumentChangedDelegate(xmlModel_DocumentChanged);
			xmlModel.DocumentLoaded -= new Action<object, EventArgs>(xmlModel_DocumentLoaded);
			xmlModel.DocumentLoading -= new Action<object, EventArgs>(xmlModel_DocumentLoading);
			xmlModel.DocumentSaved -= new Action<object, EventArgs>(xmlModel_DocumentSaved);
			xmlModel.DisplayXml -= new Action<object, DisplayXmlEventArgs>(xmlModel_Show);
			xmlModel.DocumentClosed -= new Action<object, EventArgs>(xmlModel_DocumentClosed);
			xmlModel.DocumentWellformednessChanged -= new Action<object, WellformednessEventArgs>(xmlModel_DocumentWellformednessChanged);
			xmlModel.NodeChanged -= new Action<object, NodeChangedEventArgs>(xmlModel_NodeChanged);
			xmlModel.SelectionChanged -= new Action<object, SelectionChangedEventArgs>(xmlModel_SelectionChanged);
			xmlModel.SaveXmv -= new Action<object, XmvEventArgs>(xmlModel_SaveXmv);
			xmlModel.OpenXmv -= new Action<object, XmvEventArgs>(xmlModel_OpenXmv);
			xmlModel.DocumentValidityChanged -= new Action<object, ValidityEventArgs>(xmlModel_DocumentValidityChanged);
			eventHandlersAttached = false;
		}

		#region IModel implementation
		/// <summary>
		/// Opens the desired file.
		/// </summary>
		/// <param name="filename">file to open</param>
		public void OpenFile(String filename) {
			String extension = Utils.StringUtils.GetExtension(filename);

			String directory = Path.GetDirectoryName(filename);
			if (!String.IsNullOrEmpty(directory))
				Environment.CurrentDirectory = directory;
						
			switch (extension.ToLower()) {
				case ".xml":
					FileInfo info = new FileInfo(filename);
					if (info.Length > 500 * 1024) {
						mode = Mode.BigXml;
					} else {
						mode = Mode.Xml;
					}

					if (mode == Mode.BigXml) {
						bigXmlModel.OpenFile(filename);
					} else if (mode == Mode.Xml) {
						xmlModel.OpenFile(filename);
					}
					break;
				case ".xmv":
					mode = Mode.Xml;
					if (!eventHandlersAttached)
						AttachEventHandlers();

					if (xmlModel != null) {
						xmlModel.OpenFile(filename);
					} else {
						xmlModel = new XmlModel(actionManager);
						xmlModel.OpenFile(filename);
					}
					break;
				case ".xsd":
					mode = Mode.Xsd;
					break;
				case ".dtd":
					mode = Mode.Dtd;
					break;
				default:
					Exceptions.ErrorHandling.ShowMessage(String.Format("File with the name {0} has wrong extension.", filename));
					break;
			}
		}

		/// <summary>
		/// Saves the document into the file.
		/// </summary>
		/// <param name="filename">Name of the file where to save the document.</param>
		public void SaveFile(string filename) {
			switch (mode) {
				case Mode.Xml:
					xmlModel.SaveFile(filename);
					break;
				case Mode.Xsd:

					break;
				case Mode.Dtd:

					break;
				default:
					Exceptions.ErrorHandling.ShowMessage(String.Format("File with the name {0} has wrong extension.", filename));
					break;
			}
		}

		/// <summary>
		/// Gets the xml source text.
		/// </summary>
		/// <returns>String with xml source text.</returns>
		public string GetXmlText() {
			String xmlText = "";
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null) {
						xmlText = xmlModel.GetXmlText();
					}
					break;
				case Mode.Xsd:
					if (xsdModel != null) {
						xmlText = xsdModel.GetXmlText();
					}
					break;
				case Mode.Dtd:
					break;
				case Mode.BigXml:
					if (bigXmlModel != null) {
						xmlText = bigXmlModel.GetXmlText();
					}
					break;
				default:
					break;
			}
			return xmlText;
		}

		public void Close() {
			switch (mode) {
				case Mode.Xml:
				case Mode.BigXml:
					if (xmlModel != null) {
						xmlModel.Close();
						//if (eventHandlersAttached)
						//    DetachEventHandlers();
					}
					break;
				case Mode.Xsd:
					if (xsdModel != null) {

					}
					break;
				case Mode.Dtd:
					break;
				default:
					break;
			}
		}

		public void RemoveNode(XNode node, bool recursively) {
			switch (mode) {
				case Mode.Xml:
				case Mode.BigXml:
					if (xmlModel != null) {
						xmlModel.RemoveNode(node, recursively);
					}
					break;
				default:
					break;
			}
		}
		
		public void AddNewElement(XElement parentElement, XNodeInfo newElement) {
			switch (mode) {
				case Mode.Xml:
				case Mode.BigXml:
					if (xmlModel != null) {
						xmlModel.AddNewElement(parentElement, newElement);
					}
					break;
				default:
					break;
			}
		}

		/// <summary>
		/// Connect two nodes - add childNode as a child of parentNode.
		/// </summary>
		/// <param name="parentNode"></param>
		/// <param name="childNode"></param>
		public ConnectResult ConnectNodes(XNode parentNode, XNode childNode) {
			switch (mode) {
				case Mode.Xml:
				case Mode.BigXml:
					if (xmlModel != null) 
						return xmlModel.ConnectNodes(parentNode, childNode);
					else
						return ConnectResult.None;
				default:
					return ConnectResult.None;
			}
		}

		/// <summary>
		/// Select nodes in the current document.
		/// </summary>
		/// <param name="xpathExpression">XPath expression specifying what to select.</param>
		public void XPathSelection(string xpathExpression) {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						xmlModel.XPathSelection(xpathExpression);
					break;
			}
		}

		public void SetElementName(XElement element, String name) {
			switch (mode) {
				case Mode.Xml:
				case Mode.BigXml:
					if (xmlModel != null)
						xmlModel.SetElementName(element, name);
					break;
			}
		}

		public void SetElementNamespace(XElement element, String newNamespace) {
			switch (mode) {
				case Mode.Xml:
				case Mode.BigXml:
					if (xmlModel != null)
						xmlModel.SetElementNamespace(element, newNamespace);
					break;
			}
		}

		public void UpdateAttributes(XElement element) {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						xmlModel.UpdateAttributes(element);
					break;
			}
		}

		public void AddGuidAttributes() {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						xmlModel.AddGuidAttributes();
					break;
			}
		}

		public void RemoveGuidAttributes() {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						xmlModel.RemoveGuidAttributes();
					break;
			}
		}

		public void SetMode(Mode mode) {
			this.mode = mode;
			OnModeChanged();
		}

		public Mode GetMode() {
			return this.mode;
		}

		public XElement Serialize() {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						return xmlModel.Serialize();
					else
						return null;
				default:
					return null;
			}
		}

		/// <summary>
		/// Deserializes XmlModel from Xmv file.
		/// </summary>
		/// <param name="root">root element with serialized XmlModel.</param>
		/// <exception cref="System.IO.FileFormatException">File is corrupted.</exception>
		public void Deserialize(XElement root) {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						xmlModel.Deserialize(root);
					break;
			}
		}

		public XDocument GetDocument() {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						return xmlModel.GetDocument();
					else
						return null;
				default:
					return null;
			}
		}

		public IEnumerable<XElement> GetDocumentParts() {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						return xmlModel.GetDocumentParts();
					else
						return null;
				default:
					return null;
			}
		}

		public void AddNodeInfo(XNodeInfo info) {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						xmlModel.AddNodeInfo(info);
					break;
			}
		}

		public void UpdateOrder(XElement parentElement, IEnumerable<XNode> childNodes) {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						xmlModel.UpdateOrder(parentElement, childNodes);
					break;
			}
		}

		public void Reset() {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						xmlModel.Reset();
					break;
			}
		}

		public void SetNamespacePrefix(XElement element, String prefix, XNamespace nameSpace) {
			switch (mode) {
				case Mode.Xml:
					if (xmlModel != null)
						xmlModel.SetNamespacePrefix(element, prefix, nameSpace);
					break;
			}
		}

		public void Expand(XElement element, ElementAddress address, String range, int depth) {
			switch (mode) {
				case Mode.BigXml:
					if (bigXmlModel != null)
						bigXmlModel.Expand(element, address, range, depth);
					break;
			}
		}
		#endregion
		#endregion
	}

	public delegate void DocumentChangedDelegate(object sender, DocumentChangedEventArgs e);

	public class DisplayXmlEventArgs : EventArgs {
		#region Constructors
		public DisplayXmlEventArgs(XNodeCollection nodes, bool clearCanvas) {
			if (nodes != null)
				Nodes = nodes;
			else
				throw new ArgumentNullException("nodes");

			ClearCanvas = clearCanvas;
			ConnectWithAlreadyPlaced = false;
		}

		public DisplayXmlEventArgs(XNodeCollection nodes, bool clearCanvas, bool connectWithAlreadyPlaced)
			: this(nodes, clearCanvas) {
			ConnectWithAlreadyPlaced = connectWithAlreadyPlaced;
		}
		#endregion

		#region Properties
		public XNodeCollection Nodes { get; set; }
		public bool ClearCanvas { get; set; }
		public bool ConnectWithAlreadyPlaced { get; set; }
		#endregion
	}

	public class WellformednessEventArgs : EventArgs {
		#region Constructors
		public WellformednessEventArgs(bool wellformed) {
			Wellformed = wellformed;
		}
		#endregion

		#region Properties
		public bool Wellformed { get; set; }
		#endregion
	}

	public class ValidityEventArgs : EventArgs {
		#region Constructors
		public ValidityEventArgs(bool valid) {
			Valid = valid;
		}
		#endregion

		#region Properties
		public bool Valid { get; set; }
		#endregion
	}

	public class NodeChangedEventArgs : EventArgs {
		#region Constructors
		public NodeChangedEventArgs(XNode oldNode, XNode newNode) {
			OldNode = oldNode;
			NewNode = newNode;
		}
		#endregion

		#region Properties
		public XNode OldNode { get; set; }
		public XNode NewNode { get; set; }
		#endregion
	}

	public class SelectionChangedEventArgs : EventArgs {
		#region Constructors
		public SelectionChangedEventArgs(IEnumerable<XObject> objects) {
			Objects = objects;
		}
		#endregion

		#region Properties
		public IEnumerable<XObject> Objects { get; set; }
		#endregion
	}

	public class DocumentChangedEventArgs : EventArgs {
		#region Constructors
		public DocumentChangedEventArgs() { }

		public DocumentChangedEventArgs(XmlNodeType changeType, XObject newValue, XObject oldValue)
			: this() {
			ChangeType = changeType;
			NewValue = newValue;
			OldValue = oldValue;
		}
		#endregion

		#region Properties
		public XmlNodeType ChangeType { get; set; }
		public XObject NewValue { get; set; }
		public XObject OldValue { get; set; }
		#endregion
	}

	public class XmvEventArgs : EventArgs {
		#region Constructors
		public XmvEventArgs(string filename) {
			this.Filename = filename;
		}
		#endregion

		#region Properties
		public string Filename { get; set; }
		#endregion
	}
}